﻿using System;
using System.Collections.ObjectModel;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Windows.Forms;
using Simhopp.Common;
using MessageBox = System.Windows.MessageBox;

namespace Simhopp.Client
{
    public sealed class ServerListener
    {
        public TcpClient TcpClient { get; private set; }
        private readonly Presenter _presenter;
        private Stream _stream;
        private BinaryFormatter _binaryFormatter;
        private int _clientNo;
        private static int _noOfClients = 0;
        private IPEndPoint endPoint;

        /// <summary>
        /// Creates a new ServerListener
        /// </summary>
        /// <param name="presenter">The presenter for the program</param>
        public ServerListener(Presenter presenter)
        {
            _presenter = presenter;
            endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 123);
            Reconnect();
        }
        /// <summary>
        /// Attenmts to connect to the server. If this fails, the function will ask for user input and then reattempt as long as the user wants to.
        /// </summary>
        public void Reconnect()
        {
            try
            {
                Console.WriteLine(@"Connecting to {0}, {1}", endPoint.Address, endPoint.Port);
                TcpClient = new TcpClient();
                TcpClient.Connect(endPoint);
                InitializeStream();
                StartThread();
            }
            catch (Exception e)
            {
                Console.WriteLine(@"ServerListener could not connect to server....." + e.Message);
                var controlInputMap = new Collection<MutableKeyValuePair<String, Object>>
                {
                    new MutableKeyValuePair<String, Object>("IP-adress", endPoint.Address.ToString()),
                    new MutableKeyValuePair<String, Object>("Portnummer", endPoint.Port)
                };
                var b = new InputBox("Anslutningsfel!",
                    String.Format(
                        "Fjärrservern på {0}:{1} svarade inte på anropet. Var vänlig ange korrekta uppgifter ovan. Om du inte vet vilka uppgifter det ska vara, kontakta tävlingsadministratören.",
                        endPoint.Address, endPoint.Port),
                    controlInputMap);
                if (b.ShowDialog() == true)
                {
                    ConnectToAlternativeIp((string) controlInputMap[0].Value, (int) controlInputMap[1].Value);
                }
            }
        }

        /// <summary>
        /// Attempts to connect to the non-standard IP.
        /// </summary>
        /// <param name="ip">The IPaddress of the host</param>
        /// <param name="port">The port-number of the remote socket</param>
        public void ConnectToAlternativeIp(String ip, int port)
        {
            endPoint = new IPEndPoint(IPAddress.Parse(ip), port);
            Reconnect();
        }

        /// <summary>
        /// Initializes the streams for communication.
        /// </summary>
        private void InitializeStream()
        {
            _stream = TcpClient.GetStream();
            _binaryFormatter = new BinaryFormatter();
            _clientNo = _noOfClients++;
            
        }
        /// <summary>
        /// Starts the listening thread.
        /// </summary>
        private void StartThread()
        {
            var newThread = new Thread(Run);
            newThread.Start();
        }

        /// <summary>
        /// Handles the state of the listener, as well as reading and responding to data.
        /// </summary>
        private void Run()
        {
            while (true)
            {
                do
                {
                    if (_presenter.ActiveJudge.AccountType != AccountTypes.NoAccess)
                    {
                        _presenter.UserInfo = String.Format("Du är inloggad som {0} med åtkomstnivå {1}",
                        _presenter.ActiveJudge.UserName,
                        _presenter.ActiveJudge.AccountType);
                    }
                    else
                    {
                        _presenter.UserInfo = "Du är ej inloggad";
                    }
                    if (TcpClient.Connected && _stream.CanRead)
                    {
                        try
                        {
                            ReceiveObject(_stream);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(@"Client disconnected");
                            MyLogger.Debug("Client disconnected - " + e.Message);
                        }
                    }
                } while (TcpClient.Connected);

                
                // Reconnection
                var countDown = 30;
                do
                {
                    _presenter.UserInfo = String.Format("Återanslutningsförsök {0}...", countDown);
                    TcpClient = new TcpClient();
                    try
                    {
                        TcpClient.Connect(endPoint);
                        InitializeStream();
                    }
                    catch (Exception e)
                    {
                    }
                    if(!TcpClient.Connected)
                        Thread.Sleep(10000);
                    countDown--;
                } while (countDown > 0 && TcpClient.Connected == false);

                if (TcpClient.Connected)
                {
                    _presenter.UserInfo = "Återanslutningen lyckades! Återupptar session...";
                    var logonInfo = new LogOnInfo(_presenter.ActiveJudge.UserName, "");
                    if (!logonInfo.UserName.Equals(""))
                        SendObject(new NetworkCommand(Commands.Login, logonInfo));
                    Thread.Sleep(5000);
                    continue;
                }
                _presenter.UserInfo = "Återanslutningen misslyckades";
                break; // Reconnection failed
            }
        }

        /// <summary>
        /// Returns a string with a client number. @Deprecated
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Client: " + _clientNo;
        }

        /// <summary>
        /// Handles an incoming object.
        /// </summary>
        /// <param name="stream">The stream with the object</param>
        private void ReceiveObject(Stream stream)
        {
            try
            {
                var command = _binaryFormatter.Deserialize(stream) as NetworkCommand;
                if (command != null)
                    _presenter.ProcessObject(command);
            }
            catch (Exception e)
            {
                MyLogger.Debug(String.Format("Error deserializing object: {0}\n{1}", e.Message, e.StackTrace));
                TcpClient.Close();
            }
        }
        /// <summary>
        /// Sends an object to the remote host
        /// </summary>
        /// <param name="o">The networkCommand to send</param>
        public void SendObject(NetworkCommand o)
        {
            if (!TcpClient.Connected || !_stream.CanWrite) return;
            try
            {
                _binaryFormatter.Serialize(_stream, o);
            }
            catch (Exception e)
            {
                Console.WriteLine(@"Client disconnected");
                Console.WriteLine("Client disconnected - \n" + e.Message + "\n" + e.StackTrace);
                if ((e as SerializationException) != null)
                    Console.WriteLine(@"Serialiseringsfel: " + (e as SerializationException).Source + "\n" +
                                      (e as SerializationException).Data);
            }
        }

        /// <summary>
        /// Graceful shutdown.
        /// </summary>
        public void Stop()
        {
            TcpClient.Close();
        }


    }
}
